package org.geometerplus.fbreader.formats.ceb;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import logic.bean.TOCItem;
import logic.util.FileUtil;
import logic.xml.parser.NCXHandler;

import org.geometerplus.fbreader.bookmodel.BookModel;
import org.geometerplus.fbreader.bookmodel.BookReader;
import org.geometerplus.fbreader.bookmodel.BookReadingException;
import org.geometerplus.fbreader.bookmodel.FBTextKind;
import org.geometerplus.fbreader.formats.oeb.XHTMLImageFinder;
import org.geometerplus.fbreader.formats.util.MiscUtil;
import org.geometerplus.fbreader.formats.xhtml.XHTMLReader;
import org.geometerplus.zlibrary.core.constants.XMLNamespaces;
import org.geometerplus.zlibrary.core.filesystem.ZLFile;
import org.geometerplus.zlibrary.core.image.ZLFileImage;
import org.geometerplus.zlibrary.core.util.MimeType;
import org.geometerplus.zlibrary.core.xml.ZLStringMap;
import org.geometerplus.zlibrary.core.xml.ZLXMLReaderAdapter;


/**
 * ceb 格式的书读取器
 * 
 * @author zouqp
 *         2014-09-28
 */

class Reference
{
	public final String Title;
	public final String HRef;
	
	public Reference(String title, String href) {
		Title = title;
		HRef = href;
	}
}

class CEBBookReader
        extends
        ZLXMLReaderAdapter
        implements
        XMLNamespaces
{
	private static final char[]           Dots            = new char[] {
	        '.',
	        '.',
	        '.'
	                                                      };
	
	private final BookReader              myModelReader;
	private final HashMap<String, String> myIdToHref      = new HashMap<String, String>();
	private final ArrayList<String>       myHtmlFileNames = new ArrayList<String>();
	private final ArrayList<Reference>    myTourTOC       = new ArrayList<Reference>();
	private final ArrayList<Reference>    myGuideTOC      = new ArrayList<Reference>();
	
	private String                        myOPFSchemePrefix;
	private String                        myFilePrefix;
	private String                        myNCXTOCFileName;
	private String                        myCoverFileName;
	private boolean                       isDownloadedBook;
	
	CEBBookReader(BookModel model) {
		myModelReader = new BookReader(model);
		model.setLabelResolver(new BookModel.LabelResolver()
		{
			public List<String> getCandidates(String id) {
				final int index = id.indexOf("#");
				return index > 0 ? Collections.<String> singletonList(id.substring(0, index)) : Collections.<String> emptyList();
			}
		});
		
		isDownloadedBook = model.Book.getBookStatus() == 3; //下载书籍 
	}
	
	private HashMap<String, String>  myFileNumbers = new HashMap<String, String>();
	private HashMap<String, Integer> myTOCLabels   = new HashMap<String, Integer>();
	
	void readBook(ZLFile file) throws BookReadingException {
		myFilePrefix = MiscUtil.htmlDirectoryPrefix(file);
		
		//		myIdToHref.clear();
		//		myHtmlFileNames.clear();
		//		myNCXTOCFileName = null;
		//		myTourTOC.clear();
		//		myGuideTOC.clear();
		myState = READ_NONE;
		List<TOCItem> catalogList = null;
		if (isDownloadedBook)
		{
			catalogList = initTOC(ZLFile.createFileByPath(myFilePrefix + "book.ncx"));
		}
		else
		{
			catalogList = initTOC(ZLFile.createFileByPath(file.getPath()));
		}
		
		if (catalogList == null) { return; }
		BookModel bookModel = myModelReader.getBookModel();
		bookModel.Book.setCatalogList(catalogList);
		myModelReader.pushKind(FBTextKind.REGULAR);
		if ( isDownloadedBook ) {
    		for (TOCItem item : catalogList)
    		{
    			readChapter(file, item.getId());
    		}
		}
	}
	
	void readChapter(ZLFile file, String chapterid) throws BookReadingException {
		BookModel bookModel = myModelReader.getBookModel();
		String filePath = "" ;
		if (isDownloadedBook)
		{
			String filePrefix = MiscUtil.htmlDirectoryPrefix(file);
			String name = "";
			List<TOCItem> catalogList = bookModel.Book.getCatalogList();
			for (TOCItem item : catalogList)
			{
				if (chapterid.equalsIgnoreCase(item.getId()))
				{
					name = item.getContentSrc();
					name = name.startsWith("/") ? name : "/" + name;
					break;
				}
			}
			filePath = filePrefix + name;
		}
		else
		{
			filePath = FileUtil.OnlinePath(String.valueOf(bookModel.Book.getContentId())) + chapterid + ".html";
		}
		bookModel.setCurrentChapterid(chapterid);
		bookModel.createTextModel( );
		myModelReader.setMainTextModel();
		final ZLFile xhtmlFile = ZLFile.createFileByPath(filePath);
		final XHTMLReader reader = new XHTMLReader(myModelReader, myFileNumbers);
		final String referenceName = reader.getFileAlias(MiscUtil.archiveEntryName(xhtmlFile.getPath()));
		myModelReader.addHyperlinkLabel(referenceName);
		try
		{
			reader.readFile(xhtmlFile, referenceName + '#');
		}
		catch (IOException e)
		{
			throw new BookReadingException(e, xhtmlFile);
		}
		myModelReader.insertEndOfSectionParagraph();
	}
	
	private List<TOCItem> initTOC(ZLFile ncx) {
		List<TOCItem> catalogList = null;
		SAXParserFactory factory = SAXParserFactory.newInstance();
		SAXParser parser;
		try
		{
			parser = factory.newSAXParser();
			InputStream is = ncx.getInputStream();
			NCXHandler handle = new NCXHandler();
			parser.parse(is, handle);
			is.close();
			catalogList = handle.getNavPointList();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return catalogList;
	}
	
	private BookModel.Label getTOCLabel(String id) {
		final int index = id.indexOf('#');
		final String path = (index >= 0) ? id.substring(0, index) : id;
		final String num = myFileNumbers.get(path);
		if (num == null) { return null; }
		if (index == -1)
		{
			final Integer para = myTOCLabels.get(num);
			if (para == null) { return null; }
			return new BookModel.Label(null, para);
		}
		return myModelReader.Model.getLabel(num + id.substring(index));
	}
	
	private boolean readNCX() throws BookReadingException {
		if (myNCXTOCFileName == null) { return false; }
		
		final ZLFile ncxFile = ZLFile.createFileByPath(myFilePrefix + myNCXTOCFileName);
		if (ncxFile == null || !ncxFile.exists()) { return false; }
		
		final CEBNCXReader ncxReader = new CEBNCXReader(myModelReader);
		ncxReader.readFile(ncxFile);
		final Map<Integer, CEBNCXReader.NavPoint> navigationMap = ncxReader.navigationMap();
		if (navigationMap.isEmpty()) { return false; }
		
		int level = 0;
		for (CEBNCXReader.NavPoint point : navigationMap.values())
		{
			final BookModel.Label label = getTOCLabel(point.ContentHRef);
			int index = (label != null) ? label.ParagraphIndex : -1;
			while (level > point.Level)
			{
				myModelReader.endContentsParagraph();
				--level;
			}
			while (++level <= point.Level)
			{
				myModelReader.beginContentsParagraph(-2);
				myModelReader.addContentsData(Dots);
			}
			myModelReader.beginContentsParagraph(index);
			myModelReader.addContentsData(point.Text.toCharArray());
		}
		while (level > 0)
		{
			myModelReader.endContentsParagraph();
			--level;
		}
		
		return true;
	}
	
	private void generateTOC() throws BookReadingException {
		if (readNCX()) { return; }
		for (Reference ref : myTourTOC.isEmpty() ? myGuideTOC : myTourTOC)
		{
			final BookModel.Label label = getTOCLabel(ref.HRef);
			if (label != null)
			{
				final int index = label.ParagraphIndex;
				if (index != -1)
				{
					myModelReader.beginContentsParagraph(index);
					myModelReader.addContentsData(ref.Title.toCharArray());
					myModelReader.endContentsParagraph();
				}
			}
		}
	}
	
	private static final String MANIFEST      = "manifest";
	private static final String SPINE         = "spine";
	private static final String GUIDE         = "guide";
	private static final String TOUR          = "tour";
	private static final String SITE          = "site";
	private static final String REFERENCE     = "reference";
	private static final String ITEMREF       = "itemref";
	private static final String ITEM          = "item";
	
	private static final String COVER         = "cover";
	private static final String COVER_IMAGE   = "other.ms-coverimage-standard";
	
	private static final int    READ_NONE     = 0;
	private static final int    READ_MANIFEST = 1;
	private static final int    READ_SPINE    = 2;
	private static final int    READ_GUIDE    = 3;
	private static final int    READ_TOUR     = 4;
	
	private int                 myState;
	
	@Override
	public boolean startElementHandler(String tag, ZLStringMap xmlattributes) {
		tag = tag.toLowerCase();
		if (myOPFSchemePrefix != null && tag.startsWith(myOPFSchemePrefix))
		{
			tag = tag.substring(myOPFSchemePrefix.length());
		}
		tag = tag.intern();
		if (MANIFEST == tag)
		{
			myState = READ_MANIFEST;
		}
		else if (SPINE == tag)
		{
			myNCXTOCFileName = myIdToHref.get(xmlattributes.getValue("toc"));
			myState = READ_SPINE;
		}
		else if (GUIDE == tag)
		{
			myState = READ_GUIDE;
		}
		else if (TOUR == tag)
		{
			myState = READ_TOUR;
		}
		else if (myState == READ_MANIFEST && ITEM == tag)
		{
			final String id = xmlattributes.getValue("id");
			String href = xmlattributes.getValue("href");
			if ((id != null) && (href != null))
			{
				href = MiscUtil.decodeHtmlReference(href);
				myIdToHref.put(id, href);
			}
		}
		else if (myState == READ_SPINE && ITEMREF == tag)
		{
			final String id = xmlattributes.getValue("idref");
			if (id != null)
			{
				final String fileName = myIdToHref.get(id);
				if (fileName != null)
				{
					myHtmlFileNames.add(fileName);
				}
			}
		}
		else if (myState == READ_GUIDE && REFERENCE == tag)
		{
			final String type = xmlattributes.getValue("type");
			final String title = xmlattributes.getValue("title");
			String href = xmlattributes.getValue("href");
			if (href != null)
			{
				href = MiscUtil.decodeHtmlReference(href);
				if (title != null)
				{
					myGuideTOC.add(new Reference(title, href));
				}
				if (COVER.equals(type))
				{
					final ZLFile imageFile = ZLFile.createFileByPath(myFilePrefix + href);
					myCoverFileName = imageFile.getPath();
					final String imageName = imageFile.getLongName();
					final ZLFileImage image = XHTMLImageFinder.getCoverImage(imageFile);
					if (image != null)
					{
						myModelReader.setMainTextModel();
						myModelReader.addImageReference(imageName, (short) 0, true);
						myModelReader.addImage(imageName, image);
						myModelReader.insertEndOfSectionParagraph();
					}
					else
					{
						myCoverFileName = null;
					}
				}
				else if (COVER_IMAGE.equals(type))
				{
					final ZLFile imageFile = ZLFile.createFileByPath(myFilePrefix + href);
					myCoverFileName = imageFile.getPath();
					final String imageName = imageFile.getLongName();
					myModelReader.setMainTextModel();
					myModelReader.addImageReference(imageName, (short) 0, true);
					myModelReader.addImage(imageName, new ZLFileImage(MimeType.IMAGE_AUTO, imageFile));
					myModelReader.insertEndOfSectionParagraph();
				}
			}
		}
		else if (myState == READ_TOUR && SITE == tag)
		{
			final String title = xmlattributes.getValue("title");
			String href = xmlattributes.getValue("href");
			if ((title != null) && (href != null))
			{
				href = MiscUtil.decodeHtmlReference(href);
				myTourTOC.add(new Reference(title, href));
			}
		}
		return false;
	}
	
	@Override
	public boolean endElementHandler(String tag) {
		tag = tag.toLowerCase();
		if (myOPFSchemePrefix != null && tag.startsWith(myOPFSchemePrefix))
		{
			tag = tag.substring(myOPFSchemePrefix.length());
		}
		tag = tag.intern();
		if (MANIFEST == tag || SPINE == tag || GUIDE == tag || TOUR == tag)
		{
			myState = READ_NONE;
		}
		return false;
	}
	
	@Override
	public boolean processNamespaces() {
		return true;
	}
	
	@Override
	public void namespaceMapChangedHandler(Map<String, String> namespaceMap) {
		myOPFSchemePrefix = null;
		for (Map.Entry<String, String> entry : namespaceMap.entrySet())
		{
			if (OpenPackagingFormat.equals(entry.getValue()))
			{
				myOPFSchemePrefix = entry.getKey() + ":";
				break;
			}
		}
	}
	
	@Override
	public boolean dontCacheAttributeValues() {
		return true;
	}
}
